package lowMemoryUsage;

import engine.AlgoKMeans;
import engine.AlgoKMeans.Distance;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.SwingWorker;
import ui.UserInterface;

//Algorithme des k-moyennes
public class AlgoKMeans_LM extends SwingWorker<Void, Integer> {

    private Tweet_LM[] tweets;
    private Integer clusterCount;
    private AlgoKMeans.Distance distanceType;
    private Integer minkowskiParameter;
    private UserInterface gui;//pour update la progression

    public AlgoKMeans_LM(Tweet_LM[] tweets, Integer clusterCount, AlgoKMeans.Distance distanceType, Integer minkowskiParameter) {
        this.tweets = tweets;
        this.clusterCount = clusterCount;
        this.distanceType = distanceType;
        this.minkowskiParameter = minkowskiParameter;
    }

    public void setGui(UserInterface gui) {
        this.gui = gui;
    }

    //Retourne la distance entre deux vecteurs
    public double distance(VectorTI_LM v1, VectorTI_LM v2) {
        if (distanceType == Distance.Manhattan) {
            return VectorTI_LM.distanceManhattan(v1, v2);
        } else if (distanceType == Distance.Minkowski) {
            return VectorTI_LM.distanceMinkowski(v1, v2, minkowskiParameter);
        } else {
            return VectorTI_LM.distanceEuclidienne(v1, v2);
        }
    }


    //retourne la moyenne des vecteurs en arguments
    private VectorTI_LM average(ArrayList<VectorTI_LM> vectors) {
        VectorTI_LM result = new VectorTI_LM();

        ArrayList<Integer> differentIndexes = VectorTI_LM.allDifferentIndexes(vectors);

        //Pour chaque index possible
        double composanteSum;
        for (Integer uniqueIndex : differentIndexes) {
            composanteSum = 0d;
            //Somme de cette composante
            for (VectorTI_LM v : vectors) {
                composanteSum += v.get(uniqueIndex);
            }

            result.add(uniqueIndex, composanteSum / vectors.size());
        }

        return result;
    }
    

    @Override
    protected Void doInBackground() throws Exception {

        //Centroides
        VectorTI_LM[] centroids = new VectorTI_LM[clusterCount];

        //map qui associe à chaque centroide la liste des vecteurs les plus proches
        //la clé est l'index du centroides dans "centroids", la valeur est la liste des index des vecteurs dans "tweet"
        HashMap<Integer, ArrayList<VectorTI_LM>> mapCentroidToVectors;
        ArrayList<VectorTI_LM> currentCentroidVectors;


        //Création de clusterCount centroides placés aléatoirement
        Random rand = new Random();
        Integer randomVectorIndex;
        for (int i = 0; i < clusterCount; i++) {
            randomVectorIndex = rand.nextInt(tweets.length);//indice random de vectors

            centroids[i] = new VectorTI_LM(tweets[randomVectorIndex].getVector());
            //System.out.println(i + "  " + centroids[i]);
        }

        Double distNearestCentroid, currentDistance;
        Integer nearestCentroidIndex;
        VectorTI_LM currentVector;
        
        //tant que les centroides bougent d'au moins cette distance, on continue d'itérer
        Double distMouvementMin = 0.5;
        
        //Pour la progression
        double distMax = -1;
        double distMaxInitial = 0;
        boolean distAffected = false;
        Integer iterationCount = 0;
        Integer progressionCur = 0, progressionNew;
        
        //Boucle tant que le déplacement des centroides est supérieur à distMouvementMin
        do {
            //System.out.println("iterationCount=" + iterationCount);

            mapCentroidToVectors = new HashMap<Integer, ArrayList<VectorTI_LM>>();

            //Parcours de chaque vecteur afin de déterminer son centroide le plus proche
            for (int i = 0; i < tweets.length; i++) {

                currentVector = new VectorTI_LM(tweets[i].getVector());
                distNearestCentroid = Double.MAX_VALUE;
                nearestCentroidIndex = -42;
                //System.out.print("cVs: " + currentVector.size() + "; ");

                //System.out.print("centroids.length: " + centroids.length + "; ");
                //calcul de la distance avec chaque centroide
                for (int j = 0; j < centroids.length; j++) {
                    //System.out.println("cent.size="+centroids[j].size());
                    currentDistance = distance(centroids[j], currentVector);
                    //System.out.print("d=" + currentDistance + ", ");
                    //System.out.print("cs: " + centroids[j].size() + "; ");
                    //System.out.println("indexdiff: " + VectorTILowMem.allDifferentIndexes(centroids[j], v).size());
                    //System.out.println("dist j " + j + " : " + currentDistance);

                    if (currentDistance < distNearestCentroid) {
                        distNearestCentroid = currentDistance;
                        nearestCentroidIndex = j;
                    }
                }


                //le centroid le plus proche a été trouvé, on ajoute ce vecteur à ce centroid
                currentCentroidVectors = (mapCentroidToVectors.containsKey(nearestCentroidIndex)) ? mapCentroidToVectors.get(nearestCentroidIndex) : new ArrayList<VectorTI_LM>();
                currentCentroidVectors.add(currentVector);
                mapCentroidToVectors.put(nearestCentroidIndex, currentCentroidVectors);
            }


            //Nb vecteur par cluster
            for (Map.Entry<Integer, ArrayList<VectorTI_LM>> e : mapCentroidToVectors.entrySet()) {
                System.out.println(e.getKey() + " : " + e.getValue().size());
            }


            //On repositionne les centroides comme la moyenne de leurs vecteurs
            VectorTI_LM[] newCentroids = new VectorTI_LM[clusterCount];
            for (int i = 0; i < newCentroids.length; i++) {
                newCentroids[i] = average(mapCentroidToVectors.get(i));
                //System.out.println("avg.size= " + newCentroids[i].size());
            }

            //On mesure la distance parcourue par chaque centroide pour déterminer celui qui s'est le plus déplacé
            double dist;

            distMax = Double.MIN_VALUE;
            for (int i = 0; i < newCentroids.length; i++) {
                dist = distance(newCentroids[i], centroids[i]);
                //System.out.println("dist i = " + i + "  " + dist);
                if (dist > distMax) {
                    distMax = dist;
                }
            }
            
            //On mémorise la première "distanceMax" (pour la progression)
            if (!distAffected) {
                distMaxInitial = distMax;
                distAffected = true;
                System.out.println("distMaxInitial = " + distMaxInitial);
            }
            System.out.println("distMax = " + distMax);

            //Affectation des nouveaux centroides 
            for (int i = 0; i < newCentroids.length; i++) {
                centroids[i] = new VectorTI_LM(newCentroids[i]);
            }

            iterationCount++;
            
            progressionNew = (int) (100 * (1.0 - distMax / distMaxInitial));
            if(progressionNew > progressionCur){
                progressionCur = progressionNew;
            }
            
            System.out.println("progression=" + progressionCur);
            publish(progressionCur);
        } while (distMax > distMouvementMin);

        publish(100);
        return null;
    }

    @Override
    protected void process(List<Integer> values) {
        for (Integer val : values) {
            gui.updateBarKMeans(val);
        }
    }

    @Override
    protected void done() {
        gui.kMeansFinished();
    }
}
